<?php

/** 
 * @Author: 李红雨 - RainLee <rainlee1990@yeah.net>
 * @Date: 2022-06-21 14:12:39
 * @LastEditors: 李红雨 - RainLee <rainlee1990@yeah.net>
 * @LastEditTime: 2022-07-01 19:10:48
 * @Description: File Description
 */

namespace rainlee\authn\guard;

use rainlee\authn\Authenticatable;
use rainlee\authn\NodeDrive;
use rainlee\authn\provider\CreateUserProviders;
use rainlee\authn\Recaller;
use think\facade\Cookie;
use think\facade\Session;
use think\helper\Str;

class SessionGuard
{

    use CreateUserProviders;

    protected $guard;

    protected $provider = null;

    protected $node_drive = null;

    protected $user = null;

    protected $recallAttempted = false;

    public function __construct($name, $config)
    {
        $this->guard      = $name;
        $this->node_drive = new NodeDrive($config['node_drive']);
        $this->provider   = $this->createUserProviders($config['provider']);
    }

    /**
     * 尝试登录
     *
     * @param  array    $credentials
     * @return bool
     */
    public function attempt(array $credentials = [])
    {
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
        if (!is_null($user) && $this->hasValidCredentials($user, $credentials)) {
            $this->login($user);
            return true;
        }

        return false;
    }

    /**
     * 尝试通过ID登录
     * 
     * @param int      $id
     * @return bool
     */
    public function attemptById($id)
    {
        if (!is_null($user = $this->provider->retrieveById($id))) {
            $this->login($user);
            return true;
        }
        return false;
    }

    /**
     * 验证参数有效性
     * 
     * @param \rainlee\auth\Authenticatable|null $user 用户对象
     * @param array $credentials 参数
     * @return bool
     */
    protected function hasValidCredentials($user, $credentials)
    {
        return !is_null($user) && $this->provider->validatePassword($user, $credentials['password']);
    }

    /**
     * 登录操作
     * 
     * @param \rainlee\authn\Authenticatable $user 用户对象
     * @return void
     */
    protected function login(Authenticatable $user)
    {
        Session::set($this->getName(), $user->getAuthIdentifier());
        $this->setUser($user);
    }

    /**
     * 记住我
     * @param int $expiration 过期时间（单位/秒） <=0 为永久
     */
    public function remember($expiration = 0)
    {
        if (is_null($this->user)) {
            throw new \rainlee\authn\exception\UnauthorizedException('Unauthorized', 401);
        }
        $this->createRememberToken($this->user);
        $this->refreshRecallerCooke($this->user, $expiration);
    }

    /**
     * 创建Remember Token并保存
     * 
     * @param \rainlee\authn\Authenticatable $user 用户对象
     * @return void
     */
    protected function createRememberToken(Authenticatable $user)
    {
        $user->setRememberToken($token = Str::random(60));
        $this->provider->updateRememberToken($user, $token);
    }

    /**
     * 刷新Remember Cookie
     * 
     * @param \rainlee\authn\Authenticatable $user 用户对象
     * @param bool|int $expiration
     * @return void
     */
    protected function refreshRecallerCooke($user, $expiration = 0)
    {
        if (is_int($expiration) && $expiration > 0) {
            Cookie::set(
                $this->getRecallerName(),
                (new Recaller())->encrype($user->getAuthIdentifier() . '|' . $user->getRememberToken()),
                $expiration
            );
        } else {
            Cookie::forever(
                $this->getRecallerName(),
                (new Recaller())->encrype($user->getAuthIdentifier() . '|' . $user->getRememberToken())
            );
        }
    }

    /**
     * 获取用户信息
     */
    public function user()
    {
        // 用户存在则只接返回用户信息
        if (!is_null($this->user)) {
            return $this->user;
        }

        // 判断SESSION是否有用户标识，如果没有则尝试获取remember cookie
        $id = Session::get($this->getName());

        if (!is_null($id)) {
            $user = $this->provider->retrieveById($id);
            if ($user) {
                $this->setUser($user);
                return $user;
            }
        }

        $recaller = $this->recaller();

        if (!is_null($recaller)) {
            $user = $this->userFromRecaller($recaller);

            if ($user) {
                $this->login($user);
                return $user;
            }
        }

        return null;
    }

    /**
     * 通过remember token获取用户信息
     *
     * @param  \rainlee\auth\Recaller $recaller
     * @return mixed
     */
    protected function userFromRecaller($recaller)
    {
        if (!$recaller->valid() || $this->recallAttempted) {
            return;
        }

        $this->recallAttempted = true;

        $user = $this->provider->retrieveByRememberToken(
            $recaller->id(),
            $recaller->token()
        );

        return $user;
    }

    /**
     * 获取并解密remember token的cookie
     *
     * @return \rainlee\auth\Recaller|null
     */
    protected function recaller()
    {
        if ($recaller = Cookie::get($this->getRecallerName())) {
            return (new Recaller())->decrypt($recaller);
        }
        return;
    }

    /**
     * 验证用户是否登录
     *
     * @return bool
     */
    public function check()
    {
        if ($this->node_drive->ignored()) {
            return true;
        }

        if (is_null($this->user())) {
            throw new \rainlee\authn\exception\UnauthorizedException('Unauthorized', 401);
        }
        return true;
    }

    /**
     * 退出登录
     *
     * @return void
     */
    public function logout()
    {
        $user = $this->user();

        $this->clearUserDataFromStorage();

        if (!is_null($user)) {
            $this->clearRememberToken($user);
        }

        $this->user = null;

        $this->loggedOut = true;
    }

    /**
     * 清除登录SESSION和COOKIE
     * 
     * @return void
     */
    protected function clearUserDataFromStorage()
    {
        Session::delete($this->getName());
        if ($this->recaller()) {
            Cookie::delete($this->getRecallerName());
        }
    }

    /**
     * 清除数据库中的remember token
     * 
     * @return void
     */
    protected function clearRememberToken($user)
    {
        $this->provider->updateRememberToken($user, '');
    }

    /**
     * 获取储存用户信息的session名
     *
     * @return string
     */
    protected function getName()
    {
        return 'login_' . $this->guard . '_' . sha1(static::class);
    }

    /**
     * 获取储存"recaller"的cookie名
     *
     * @return string
     */
    protected function getRecallerName()
    {
        return 'remember_' . $this->guard . '_' . sha1(static::class);
    }

    /**
     * 设置当前用户
     * 
     * @param \rainlee\authn\Authenticatable $user
     * @return void
     */
    protected function setUser(Authenticatable $user)
    {
        $this->user = $user;
    }
}
